chore: Cherry-pick: Merkl rewards fixes and MUSD-518 CTA event (7.70.0)#27605
Conversation
…ests (#27473) <!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** **Reason for change:** In the Cash section (homepage), the "Claim bonus" CTA was shown even when the claimable mUSD bonus was less than $0.01 (e.g. dust after claiming). Small amounts like 0.007401 were also displayed as "0.01" because the formatting logic rounded to 2 decimals instead of showing "< 0.01". **Improvement / solution:** 1. **MusdAggregatedRow (Cash section):** Only show the "Claim bonus" button when the claimable reward is at least $0.01. Introduced `MIN_CLAIMABLE_BONUS_USD` and `isClaimableBonusAboveThreshold(reward)`; below that threshold the row shows the static "3% bonus" text instead of the CTA. 2. **useMerklRewards:** Removed use of `renderFromTokenMinimalUnit`, which only treated values below 0.00001 as "< 0.00001" and rounded everything else (e.g. 0.007401 → "0.01"). The hook now computes the decimal value as `unclaimedBaseUnits / 10^tokenDecimals` and formats it as `"< 0.01"` when < 0.01, otherwise `toFixed(2)`. This ensures amounts like 0.007401 display as "< 0.01" and the Cash section threshold logic works correctly. 3. **Tests:** MusdAggregatedRow tests for the claimable-bonus threshold (hide CTA for "< 0.01", "0.01", "0.005"; show for "0.02"). useMerklRewards tests updated to assert outcomes instead of mocking `renderFromTokenMinimalUnit`; added case for 7401 with 6 decimals → "< 0.01". ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: Fixed Cash section showing "Claim bonus" for amounts under $0.01 and corrected display of small claimable amounts as "< 0.01" ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/MUSD-513 ## **Manual testing steps** ```gherkin Feature: Cash section claim bonus threshold and small-amount display Scenario: Claim bonus CTA hidden when claimable amount is below $0.01 Given I am on the Wallet home screen with the Cash section visible And my claimable mUSD bonus is less than $0.01 (e.g. dust after claiming) When I view the mUSD row in the Cash section Then I should see "3% bonus" (green text) instead of "Claim bonus" And I should not see a tappable "Claim bonus" link Scenario: Claim bonus CTA shown when claimable amount is at least $0.01 Given I am on the Wallet home screen with the Cash section visible And my claimable mUSD bonus is at least $0.01 When I view the mUSD row in the Cash section Then I should see the "Claim bonus" link And tapping it should start the claim flow ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** https://github.com/user-attachments/assets/ec20c986-1694-4f45-9e8b-4bb9eaed36d0 <!-- [screenshots/recordings] --> ### **After** <img width="391" height="845" alt="Screenshot 2026-03-16 at 09 07 21" src="https://github.com/user-attachments/assets/797d36f7-bf62-47e6-a544-299becef83b0" /> <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes reward amount formatting and CTA gating logic in the Cash section; incorrect numeric conversion/rounding (notably `BigInt`→`Number`) could mis-display large rewards or edge-case decimals. > > **Overview** > **Fixes dust claim UX in the Cash section.** `MusdAggregatedRow` now only shows the **"Claim bonus"** CTA when the claimable bonus is at least `$0.01`; otherwise it shows the static **"3% bonus"** label (including when the hook returns `"< 0.01"`). > > **Simplifies Merkl claimable reward formatting.** `useMerklRewards` drops `renderFromTokenMinimalUnit` and instead computes the decimal amount directly from base units, returning `"< 0.01"` for anything below `$0.01` and `toFixed(2)` otherwise. > > **Tests updated/added.** Merkl reward tests no longer mock the formatter and add coverage for sub-$0.01 amounts (including cases that previously rounded up), and Cash row tests cover the new $0.01 CTA threshold behavior. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 1054f6f. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
) <!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** 1. **Reason for the change:** The minimum claimable bonus threshold ($0.01 USD) was implemented only in `MusdAggregatedRow`, so other consumers of `useMerklBonusClaim` (e.g. token list items) could still show "Claim bonus" for sub-threshold amounts like `"< 0.01"`. The threshold logic also lived in a single component instead of being reusable. 2. **Improvement/solution:** - Introduced a shared util `merklClaimableThreshold.ts` with `MIN_CLAIMABLE_BONUS_USD` and `isClaimableBonusAboveThreshold(reward)`. - `useMerklBonusClaim` now applies this threshold: it returns `claimableReward: null` when the raw value from `useMerklRewards` is below 0.01 or `"< 0.01"`, and passes through the string otherwise. - Removed the duplicate threshold logic from `MusdAggregatedRow`; it now uses `!!claimableReward && !hasPendingClaim` like other consumers. - Added unit tests for the threshold util and extended hook tests for the null-out-below-threshold behavior. ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: Applied a minimum $0.01 threshold for showing the "Claim bonus" CTA for Merkl rewards so that amounts below the threshold show the 3% bonus label instead. ## **Related issues** Fixes: ## **Manual testing steps** ```gherkin Feature: Merkl claimable bonus threshold Scenario: Home Cash section shows "3% bonus" when claimable reward is below $0.01 Given the user has mUSD in the Cash section and Merkl claimable reward is "< 0.01" or below 0.01 USD When the user views the Home screen Cash section Then the mUSD row shows the green "3% bonus" text and does not show "Claim bonus" Scenario: Home Cash section shows "Claim bonus" when claimable reward is at least $0.01 Given the user has mUSD in the Cash section and Merkl claimable reward is at least 0.01 USD When the user views the Home screen Cash section Then the mUSD row shows the "Claim bonus" CTA (when there is no pending claim) ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes the logic that determines when the "Claim bonus" CTA is shown by nulling out sub-$0.01 rewards at the hook level; mistakes could incorrectly hide/show the claim action across multiple surfaces. > > **Overview** > Applies a **minimum $0.01 claimable bonus threshold** consistently across all `useMerklBonusClaim` consumers by introducing `MerklRewards.utils` (`MIN_CLAIMABLE_BONUS_USD`, `isClaimableBonusAboveThreshold`) and having the hook return `claimableReward: null` for `"< 0.01"` or below-threshold values. > > Removes the duplicated threshold parsing from `MusdAggregatedRow` and simplifies it (and other consumers like `TokenListItem`) to treat `claimableReward` presence as the CTA gate. Adds unit tests for the new util and extends hook/component tests to cover the below-threshold nulling behavior. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 022add4. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
…hown cp-7.70.0 (#27353) <!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** Adds `mUSD Claim Bonus CTA Available` event that fires when the Merkl claim CTA is mounted. `mUSD Claim Bonus CTA Available` Event Properties | Property | Type | Description | |---|---|---| | `location` | `string` | Where the CTA is rendered (e.g. `token_list_item`, `home_cash_section`) | | `view_trigger` | `string` | E.g. `component_mounted` | | `button_text` | `string` | E.g. `Claim bonus` | | `network_chain_id` | `string` | Chain ID of the token's network | | `network_name` | `string` | Human-readable network name (e.g. `Ethereum Mainnet`) | | `asset_symbol` | `string` | Token symbol (e.g. `mUSD`) | | `bonus_amount_range` | `string` | Bucketed reward value: `< 0.01`, `0.01 - 0.99`, `1.00 - 9.99`, `10.00 - 99.99`, `100.00 - 999.99`, `1000.00+` | | `has_claimed_before` | `boolean` | Whether the user has previously claimed a Merkl reward for this token | <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: added mUSD Claim Bonus CTA Available event that fires when the Merkl claim CTA is mounted. ## **Related issues** Fixes: [MUSD-518: Mobile - Add segment event which tracks when the claim CTA is shown and how much the user would have to claim](https://consensyssoftware.atlassian.net/browse/MUSD-518) ## **Manual testing steps** ```gherkin Feature: Merkl claim bonus CTA impression tracking Scenario: user sees claim bonus CTA in token list Given user has a claimable Merkl reward on an eligible token When the token list item scrolls into view Then a "mUSD Claim Bonus CTA Available" event fires once with reward range, location, and network metadata Scenario: user sees claim bonus CTA in home cash section Given user holds mUSD on Linea and has a claimable Merkl bonus When the mUSD aggregated row is visible on the home screen Then a "mUSD Claim Bonus CTA Available" event fires once with location "home_cash_section" ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** N/A -`mUSD Claim Bonus CTA Available` event doesn't exist <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> https://github.com/user-attachments/assets/bf04a706-7a64-42a3-b21a-e522cbc7b38b ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Adds new analytics emission tied to token-list viewport visibility and extends Merkl reward hooks, which could impact event volume/accuracy and token list rendering performance if mis-triggered. > > **Overview** > Adds a new MetaMetrics event, `MUSD_CLAIM_BONUS_CTA_DISPLAYED`, fired from `useMerklBonusClaim` **once per mount** when a Merkl bonus is claimable, there is no pending claim, and the CTA is *visible*; the event includes location, network metadata, a bucketed `bonus_amount_range`, and a new `has_claimed_before` flag derived from on-chain claimed amount. > > Updates token list rows (`TokenListItem`/`TokenListItemV2`) and the home cash section to pass `location` and viewport visibility into `useMerklBonusClaim`, and teaches `TokenList` (FlashList mode) to track visible items via `onViewableItemsChanged` and propagate `isVisible` down. Tests are expanded/updated to cover the new hook signature, impression gating, and reward-range bucketing. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 784b6b7. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Matthew Grainger <matthew.grainger@consensys.net> Co-authored-by: Matthew Grainger <46547583+Matt561@users.noreply.github.com>
|
CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
|
Please don't merge this cherry-pick in its current state. I'm investigating a crash that's happening in main from this PR - #27353 |
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** Fixes a crash in the `TokenList` component when switching networks. ### Cause of Bug useLayoutEffect with recomputeViewableItems() fires synchronously after FlashList's own useLayoutEffect truncates its internal layouts array to match the new (shorter) data. But FlashList's ViewabilityHelper still holds possiblyViewableIndices from the old (longer) data — those only get reset later via computeItemViewability() in a useEffect. So recomputeViewableItems() iterates stale indices, calls getLayout(index) where index >= layouts.length, and FlashList throws "index out of bounds, not enough layouts". ### Bug Fix Remove the useLayoutEffect. This is safe because extraData already includes isTokenNetworkFilterEqualCurrentNetwork, which triggers FlashList to re-render and internally recompute viewability with valid indices on its own schedule. <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: fix TokenList crash when switching networks ## **Related issues** Fixes: #27651 (comment) ## **Manual testing steps** ```gherkin Feature: Token list stability on network switch Scenario: user switches network on "Cash" or "Tokens" token list without crash Given user is on the token list full view with tokens visible When user switches network filter (e.g. "Popular Networks" to "Linea") Then the token list updates without crashing Scenario: user switches network on Tokens full view without crash Given user is on the token list full view with multiple tokens visible When user switches network filter Then the token list updates without crashing ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> <img width="1206" height="2622" alt="image" src="https://github.com/user-attachments/assets/ffe6255f-43c5-4f64-bee7-2b8f03106681" /> ### **After** <!-- [screenshots/recordings] --> https://github.com/user-attachments/assets/21d332be-13b6-4e2a-a378-2a1afa1a54c5 ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Small UI lifecycle change limited to `TokenList`; risk is low but could affect how quickly FlashList updates viewability after network/filter changes. > > **Overview** > Prevents a crash when changing networks by removing the `useLayoutEffect` that forced `FlashList` to call `recomputeViewableItems()` when the token network filter/current network equality changed. > > This also drops the unused `useLayoutEffect` import, leaving list updates to normal `FlashList` rendering and `extraData` updates. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 67fa172. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
5692727 to
e2f3bd5
Compare
🔍 Smart E2E Test Selection⏭️ Smart E2E selection skipped - base branch is not main (base: release/7.70.0) All E2E tests pre-selected. |
|
✅ E2E Fixture Validation — Schema is up to date |
|



Summary
Cherry-pick of three merged PRs from
mainintorelease/7.70.0:useMerklBonusClaim)Related
CHANGELOG entry: null
Note
Medium Risk
Modifies Merkl reward/claim CTA behavior and token list rendering by introducing new thresholding, viewability-driven state, and analytics side effects, which could impact when users see or can tap “Claim bonus.” Risk is mitigated by extensive new unit tests but touches frequently-rendered UI paths.
Overview
Adds a shared $0.01 minimum threshold for displaying the Merkl “Claim bonus” CTA via new
MerklRewards.utilshelpers, so amounts like"< 0.01"or values below0.01are treated as non-claimable.Updates
useMerklRewardsto simplify reward formatting (including consistent"< 0.01"handling) and to surfacehasClaimedBefore, then updatesuseMerklBonusClaimto (1) null out sub-threshold rewards and (2) emit a new analytics eventMUSD_CLAIM_BONUS_CTA_DISPLAYEDonce per mount when the CTA is eligible and visible.Plumbs visibility from
TokenList(FlashList viewability tracking) down intoTokenListItem/TokenListItemV2and other call sites to avoid firing CTA analytics when off-screen, and adds minor UI tweaks (center-align blocking alert text, adjust percentage row styling).Written by Cursor Bugbot for commit 569272724a983842fec6e932df30c0020e75531b. This will update automatically on new commits. Configure here.